/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

import com.replica.replicaisland.CollisionParameters.HitType;
import com.replica.replicaisland.GameObject.ActionType;

public class NPCComponent extends GameComponent {
	private float mPauseTime;
	private float mTargetXVelocity;
	private int mLastHitTileX;
	private int mLastHitTileY;

	private int mDialogEvent;
	private int mDialogIndex;

	private HitReactionComponent mHitReactComponent;

	private int[] mQueuedCommands;
	private int mQueueTop;
	private int mQueueBottom;
	private boolean mExecutingQueue;

	private Vector2 mPreviousPosition;

	private float mUpImpulse;
	private float mDownImpulse;
	private float mHorizontalImpulse;
	private float mSlowHorizontalImpulse;
	private float mAcceleration;

	private int mGameEvent;
	private int mGameEventIndex;
	private boolean mSpawnGameEventOnDeath;

	private boolean mReactToHits;
	private boolean mFlying;
	private boolean mPauseOnAttack;

	private float mDeathTime;
	private float mDeathFadeDelay;

	private static final float UP_IMPULSE = 400.0f;
	private static final float DOWN_IMPULSE = -10.0f;
	private static final float HORIZONTAL_IMPULSE = 200.0f;
	private static final float SLOW_HORIZONTAL_IMPULSE = 50.0f;
	private static final float ACCELERATION = 300.0f;
	private static final float HIT_IMPULSE = 300.0f;
	private static final float HIT_ACCELERATION = 700.0f;

	private static final float DEATH_FADE_DELAY = 4.0f;

	private static final float PAUSE_TIME_SHORT = 1.0f;
	private static final float PAUSE_TIME_MEDIUM = 4.0f;
	private static final float PAUSE_TIME_LONG = 8.0f;
	private static final float PAUSE_TIME_ATTACK = 1.0f;
	private static final float PAUSE_TIME_HIT_REACT = 1.0f;

	private static final int COMMAND_QUEUE_SIZE = 16;

	public NPCComponent() {
		super();
		setPhase(ComponentPhases.THINK.ordinal());
		mQueuedCommands = new int[COMMAND_QUEUE_SIZE];
		mPreviousPosition = new Vector2();
		reset();
	}

	@Override
	public void reset() {
		mPauseTime = 0.0f;
		mTargetXVelocity = 0.0f;
		mLastHitTileX = 0;
		mLastHitTileY = 0;
		mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
		mDialogIndex = 0;
		mHitReactComponent = null;
		mQueueTop = 0;
		mQueueBottom = 0;
		mPreviousPosition.zero();
		mExecutingQueue = false;
		mUpImpulse = UP_IMPULSE;
		mDownImpulse = DOWN_IMPULSE;
		mHorizontalImpulse = HORIZONTAL_IMPULSE;
		mSlowHorizontalImpulse = SLOW_HORIZONTAL_IMPULSE;
		mAcceleration = ACCELERATION;
		mGameEvent = -1;
		mGameEventIndex = -1;
		mSpawnGameEventOnDeath = false;
		mReactToHits = false;
		mFlying = false;
		mDeathTime = 0.0f;
		mDeathFadeDelay = DEATH_FADE_DELAY;
		mPauseOnAttack = true;
	}

	@Override
	public void update(float timeDelta, BaseObject parent) {

		GameObject parentObject = (GameObject) parent;

		if (mReactToHits && mPauseTime <= 0.0f
				&& parentObject.getCurrentAction() == ActionType.HIT_REACT) {
			mPauseTime = PAUSE_TIME_HIT_REACT;
			pauseMovement(parentObject);
			parentObject.getVelocity().x = -parentObject.facingDirection.x
					* HIT_IMPULSE;
			parentObject.getAcceleration().x = HIT_ACCELERATION;

		} else if (parentObject.getCurrentAction() == ActionType.DEATH) {
			if (mSpawnGameEventOnDeath && mGameEvent != -1) {
				if (Utils.close(parentObject.getVelocity().x, 0.0f)
						&& parentObject.touchingGround()) {

					if (mDeathTime < mDeathFadeDelay
							&& mDeathTime + timeDelta >= mDeathFadeDelay) {
						HudSystem hud = sSystemRegistry.hudSystem;

						if (hud != null) {
							hud.startFade(false, 1.5f);
							hud.sendGameEventOnFadeComplete(mGameEvent,
									mGameEventIndex);
							mGameEvent = -1;
						}
					}
					mDeathTime += timeDelta;

				}
			}
			// nothing else to do.
			return;
		} else if (parentObject.life <= 0) {
			parentObject.setCurrentAction(ActionType.DEATH);
			parentObject.getTargetVelocity().x = 0;
			return;
		} else if (parentObject.getCurrentAction() == ActionType.INVALID
				|| (!mReactToHits && parentObject.getCurrentAction() == ActionType.HIT_REACT)) {
			parentObject.setCurrentAction(ActionType.MOVE);
		}

		if (mPauseTime <= 0.0f) {

			HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem;

			if (hotSpotSystem != null) {
				final float centerX = parentObject.getCenteredPositionX();
				final int hitTileX = hotSpotSystem.getHitTileX(centerX);
				final int hitTileY = hotSpotSystem.getHitTileY(parentObject
						.getPosition().y + 10.0f);
				boolean accepted = true;

				if (hitTileX != mLastHitTileX || hitTileY != mLastHitTileY) {

					final int hotSpot = hotSpotSystem.getHotSpotByTile(
							hitTileX, hitTileY);

					if (hotSpot >= HotSpotSystem.HotSpotType.NPC_GO_RIGHT
							&& hotSpot <= HotSpotSystem.HotSpotType.NPC_SLOW) {
						// movement-related commands are immediate
						parentObject.setCurrentAction(ActionType.MOVE);
						accepted = executeCommand(hotSpot, parentObject,
								timeDelta);
					} else if (hotSpot == HotSpotSystem.HotSpotType.ATTACK
							&& !mPauseOnAttack) {
						// when mPauseOnAttack is false, attacks are also
						// immediate.
						accepted = executeCommand(hotSpot, parentObject,
								timeDelta);
					} else if (hotSpot == HotSpotSystem.HotSpotType.NPC_RUN_QUEUED_COMMANDS) {
						if (!mExecutingQueue && mQueueTop != mQueueBottom) {
							mExecutingQueue = true;
						}
					} else if (hotSpot > HotSpotSystem.HotSpotType.NONE) {
						queueCommand(hotSpot);
					}
				}

				if (mExecutingQueue) {
					if (mQueueTop != mQueueBottom) {
						accepted = executeCommand(nextCommand(), parentObject,
								timeDelta);
						if (accepted) {
							advanceQueue();
						}
					} else {
						mExecutingQueue = false;
					}
				}

				if (accepted) {
					mLastHitTileX = hitTileX;
					mLastHitTileY = hitTileY;
				}

			}
		} else {
			mPauseTime -= timeDelta;
			if (mPauseTime < 0.0f) {
				resumeMovement(parentObject);
				mPauseTime = 0.0f;
				parentObject.setCurrentAction(ActionType.MOVE);
			}
		}

		mPreviousPosition.set(parentObject.getPosition());
	}

	private boolean executeCommand(int hotSpot, GameObject parentObject,
			float timeDelta) {
		boolean hitAccepted = true;
		final CameraSystem camera = sSystemRegistry.cameraSystem;

		switch (hotSpot) {
		case HotSpotSystem.HotSpotType.WAIT_SHORT:
			if (mPauseTime == 0.0f) {
				mPauseTime = PAUSE_TIME_SHORT;
				pauseMovement(parentObject);
			}
			break;
		case HotSpotSystem.HotSpotType.WAIT_MEDIUM:
			if (mPauseTime == 0.0f) {
				mPauseTime = PAUSE_TIME_MEDIUM;
				pauseMovement(parentObject);
			}
			break;
		case HotSpotSystem.HotSpotType.WAIT_LONG:
			if (mPauseTime == 0.0f) {
				mPauseTime = PAUSE_TIME_LONG;
				pauseMovement(parentObject);
			}
			break;
		case HotSpotSystem.HotSpotType.ATTACK:
			if (mPauseOnAttack) {
				if (mPauseTime == 0.0f) {
					mPauseTime = PAUSE_TIME_ATTACK;
					pauseMovement(parentObject);

				}
			}
			parentObject.setCurrentAction(ActionType.ATTACK);

			break;

		case HotSpotSystem.HotSpotType.TALK:
			if (mHitReactComponent != null) {
				if (parentObject.lastReceivedHitType != HitType.COLLECT) {
					mHitReactComponent.setSpawnGameEventOnHit(HitType.COLLECT,
							mDialogEvent, mDialogIndex);
					if (parentObject.getVelocity().x != 0.0f) {
						pauseMovement(parentObject);
					}
					hitAccepted = false;
				} else {
					parentObject.setCurrentAction(ActionType.MOVE);

					resumeMovement(parentObject);
					mHitReactComponent.setSpawnGameEventOnHit(HitType.INVALID,
							0, 0);
					parentObject.lastReceivedHitType = HitType.INVALID;
				}
			}
			break;

		case HotSpotSystem.HotSpotType.WALK_AND_TALK:
			if (mDialogEvent != GameFlowEvent.EVENT_INVALID) {
				LevelSystem level = sSystemRegistry.levelSystem;
				level.sendGameEvent(mDialogEvent, mDialogIndex, true);
				mDialogEvent = GameFlowEvent.EVENT_INVALID;
			}
			break;

		case HotSpotSystem.HotSpotType.TAKE_CAMERA_FOCUS:
			if (camera != null) {
				camera.setTarget(parentObject);
			}
			break;

		case HotSpotSystem.HotSpotType.RELEASE_CAMERA_FOCUS:

			if (camera != null) {
				GameObjectManager gameObjectManager = sSystemRegistry.gameObjectManager;
				camera.setTarget(gameObjectManager.getPlayer());
			}
			break;

		case HotSpotSystem.HotSpotType.END_LEVEL:
			HudSystem hud = sSystemRegistry.hudSystem;

			if (hud != null) {
				hud.startFade(false, 1.5f);
				hud.sendGameEventOnFadeComplete(
						GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0);
			}
			break;
		case HotSpotSystem.HotSpotType.GAME_EVENT:
			if (mGameEvent != -1) {
				LevelSystem level = sSystemRegistry.levelSystem;
				if (level != null) {
					level.sendGameEvent(mGameEvent, mGameEventIndex, true);
					mGameEvent = -1;
				}
			}
			break;

		case HotSpotSystem.HotSpotType.NPC_GO_UP_FROM_GROUND:
			if (!parentObject.touchingGround()) {
				hitAccepted = false;
				break;
			}
			// fall through
		case HotSpotSystem.HotSpotType.NPC_GO_UP:
			parentObject.getVelocity().y = mUpImpulse;
			parentObject.getTargetVelocity().y = 0.0f;
			mTargetXVelocity = 0.0f;

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_DOWN_FROM_CEILING:
			if (!parentObject.touchingCeiling()) {
				hitAccepted = false;
				break;
			}
			// fall through
		case HotSpotSystem.HotSpotType.NPC_GO_DOWN:
			parentObject.getVelocity().y = mDownImpulse;
			parentObject.getTargetVelocity().y = 0.0f;
			if (mFlying) {
				mTargetXVelocity = 0.0f;
			}
			break;
		case HotSpotSystem.HotSpotType.NPC_GO_LEFT:
			parentObject.getTargetVelocity().x = -mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;
			if (mFlying) {
				parentObject.getVelocity().y = 0.0f;
				parentObject.getTargetVelocity().y = 0.0f;
			}
			break;
		case HotSpotSystem.HotSpotType.NPC_GO_RIGHT:
			parentObject.getTargetVelocity().x = mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;
			if (mFlying) {
				parentObject.getVelocity().y = 0.0f;
				parentObject.getTargetVelocity().y = 0.0f;
			}

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_UP_RIGHT:
			parentObject.getVelocity().y = mUpImpulse;
			parentObject.getTargetVelocity().x = mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_UP_LEFT:
			parentObject.getVelocity().y = mUpImpulse;
			parentObject.getTargetVelocity().x = -mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_DOWN_RIGHT:
			parentObject.getVelocity().y = mDownImpulse;
			parentObject.getTargetVelocity().x = mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_DOWN_LEFT:
			parentObject.getVelocity().y = mDownImpulse;
			parentObject.getTargetVelocity().x = -mHorizontalImpulse;
			parentObject.getAcceleration().x = mAcceleration;

			break;
		case HotSpotSystem.HotSpotType.NPC_GO_TOWARDS_PLAYER:
			int direction = 1;
			GameObjectManager manager = sSystemRegistry.gameObjectManager;
			if (manager != null) {
				GameObject player = manager.getPlayer();
				if (player != null) {
					direction = Utils.sign(player.getCenteredPositionX()
							- parentObject.getCenteredPositionX());
				}
			}
			parentObject.getTargetVelocity().x = mHorizontalImpulse * direction;
			if (mFlying) {
				parentObject.getVelocity().y = 0.0f;
				parentObject.getTargetVelocity().y = 0.0f;
			}
			break;
		case HotSpotSystem.HotSpotType.NPC_GO_RANDOM:
			parentObject.getTargetVelocity().x = mHorizontalImpulse
					* (Math.random() > 0.5f ? -1.0f : 1.0f);
			if (mFlying) {
				parentObject.getVelocity().y = 0.0f;
				parentObject.getTargetVelocity().y = 0.0f;
			}
			break;

		case HotSpotSystem.HotSpotType.NPC_STOP:
			parentObject.getTargetVelocity().x = 0.0f;
			parentObject.getVelocity().x = 0.0f;
			break;

		case HotSpotSystem.HotSpotType.NPC_SLOW:
			parentObject.getTargetVelocity().x = mSlowHorizontalImpulse
					* Utils.sign(parentObject.getTargetVelocity().x);
			break;

		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_2:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_3:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_4:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_5:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_2:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_3:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_4:
		case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_5:
			selectDialog(hotSpot);
			break;
		case HotSpotSystem.HotSpotType.NONE:
			if (parentObject.touchingGround()
					&& parentObject.getVelocity().y <= 0.0f) {
				// resumeMovement(parentObject);
			}
			break;
		}

		return hitAccepted;
	}

	private void pauseMovement(GameObject parentObject) {
		mTargetXVelocity = parentObject.getTargetVelocity().x;
		parentObject.getTargetVelocity().x = 0.0f;
		parentObject.getVelocity().x = 0.0f;
	}

	private void resumeMovement(GameObject parentObject) {
		parentObject.getTargetVelocity().x = mTargetXVelocity;
		parentObject.getAcceleration().x = mAcceleration;
	}

	private void selectDialog(int hitSpot) {
		mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
		mDialogIndex = hitSpot
				- HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1;

		if (hitSpot >= HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1) {
			mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER2;
			mDialogIndex = hitSpot
					- HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1;
		}
	}

	private int nextCommand() {
		int result = HotSpotSystem.HotSpotType.NONE;
		if (mQueueTop != mQueueBottom) {
			result = mQueuedCommands[mQueueTop];
		}
		return result;
	}

	private int advanceQueue() {
		int result = HotSpotSystem.HotSpotType.NONE;
		if (mQueueTop != mQueueBottom) {
			result = mQueuedCommands[mQueueTop];
			mQueueTop = (mQueueTop + 1) % COMMAND_QUEUE_SIZE;
		}
		return result;
	}

	private void queueCommand(int hotspot) {
		int nextSlot = (mQueueBottom + 1) % COMMAND_QUEUE_SIZE;
		if (nextSlot != mQueueTop) { // only comply if there is space left in
										// the buffer
			mQueuedCommands[mQueueBottom] = hotspot;
			mQueueBottom = nextSlot;
		}
	}

	public void setHitReactionComponent(HitReactionComponent hitReact) {
		mHitReactComponent = hitReact;
	}

	public void setSpeeds(float horizontalImpulse, float slowHorizontalImpulse,
			float upImpulse, float downImpulse, float acceleration) {
		mHorizontalImpulse = horizontalImpulse;
		mSlowHorizontalImpulse = slowHorizontalImpulse;
		mUpImpulse = upImpulse;
		mDownImpulse = downImpulse;
		mAcceleration = acceleration;
	}

	public void setGameEvent(int event, int index, boolean spawnOnDeath) {
		mGameEvent = event;
		mGameEventIndex = index;
		mSpawnGameEventOnDeath = spawnOnDeath;
	}

	public void setReactToHits(boolean react) {
		mReactToHits = react;
	}

	public void setFlying(boolean flying) {
		mFlying = flying;
	}

	public void setPauseOnAttack(boolean pauseOnAttack) {
		mPauseOnAttack = pauseOnAttack;
	}
}
